package Modelo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;


public class Equation implements Serializable
{
	private static final long serialVersionUID = 1L;

	//Nome da formula.
	private String nome;
	
	/*INFIX -> Expressão que representa o formato normal de uma equação.*/
	private String infix;
	
	/*POSFIX -> Expressão processada a partir do INFIX. 
	 * Esta expressão encontra-se processada de forma a construir uma 
	 * arvore binaria capaz de resolver a equação.
	 * Esta expressão tem em conta as prioridades dos operadores.*/
	private String posfix;
	
	/*Pilha utilizada na construção da arvore.*/
	private Pilha<Nodo> pilhaAR;
	
	/*Pilha utilizada na construção da expressão posfix.*/
	private Pilha<String> pilhaST;
	
	/*Lista de variaveis associadas a esta equação. Estas váriaveis podem receber valores númericos.*/
	private List<Variavel> variaveis;
	
	/*Este atributo vai guardando o nodo anterior. Começa como null, para o primeiro nodo.
	 * Serve para a percentagem, por exemplo (5+50%) = 7.5, a percentagem é referente ao 
	 * numero anterior. Se não possuir um número anterior o resultado é 0.*/
	private Nodo lastNum;
	
	/*CONSTRUTOR: Recebe uma expressão infix como parametro.
	 * 			
	 * 	- Inicializa o posfix.
	 * 	- Inicializa as pilhas.
	 * 	- Inicializa o array das variaveis.*/
	public Equation(String infix)
	{
		this.infix = infix;
		
		nome = "";
		
		posfix = "";
		
		pilhaST = new Pilha<String>();
		
		variaveis = new ArrayList<Variavel>();
	}
	
	/*Esta função constroi a expressão posfix da infix. 
	 * Primeiro valida a expressaõ infix, se esta se encontrar valida, prosegue com a construção.*/
	public Erros criarPostfix()
	{
		String c = "";
		String numero = "";
		Erros id;
		
		id = validar();
		
		//Se valido, prosegue.
		if(id != Erros.VALIDO)
			return id;
		
		//Percorre a expressão infix, caracter a caracter.
		for(int i = 0; i < infix.length(); i++)
		{
			//c fica com o valor do caracter actual.
			c = String.valueOf(infix.charAt(i));
			
			switch(c)
			{
				
				case "+":
					//Escreve o numero na expressão posfix.
					numero = escreverNum(numero);
					
					//Atribui a este caracter uma prioridade baixa, e escreve-o na posfix conforme o conteudo da pilha.
					prioridadeBaixa(c);
					
					break;
					
				case "-":
					
					//Caso este operador apareça no inicio da equação, indica que o operando é negativo.
					if(i > 0)
					{
						numero = escreverNum(numero);
					
						prioridadeBaixa(c);
					}
					else
						numero = numero.concat(c);
					
					break;
					
				case "/":
					
					numero = escreverNum(numero);

					prioridadeMedia(c);
					
					break;
					
				case "*":
					
					numero = escreverNum(numero);
					
					//Possui prioridade media, significa que tem prioridade sobre o operador + e -
					prioridadeMedia(c);
					
					break;
					
				case "^":
					
					numero = escreverNum(numero);
					
					//Possui prioridade maxima.
					prioridadeAlta(c);
					
					break;
					
				case "(":
					
					prioridadeAlta(c);
					
					break;
					
				case ")":
					
					numero = escreverNum(numero);
					
					//Esta função remove da pilha todos os operadores entre parentesis e escreve-os na expressao posfix.
					fimParentesis();
					
					break;
					
				case "√":
					
					numero = escreverNum(numero);
					
					prioridadeAlta(c);
					
					break;
					
					default:
						//Caso o caracter seja um numero, concatena.
						numero = numero.concat(c);
						
			}
		}
		
		escreverNum(numero);
		
		//Enquanto sobrar operadores na pilha, escreve-os na expressão.
		while(pilhaST.tamanho() > 0)
		{
			posfix = posfix.concat(pilhaST.pop() + "|");
		}
		
		//Devolve o id de erro, que neste ponto deve ser sempre valido.
		return id;
	}
	
	/*Este método valida a expressão infix, devolvendo um erro especifico conforme o que estiver invalido.*/
	private Erros validar()
	{
		char c, c1, c2;
		int x = 0; 
		int z = 0;
		
		//Se a expressão infix for nula, devolve o erro.
		if(infix == null)
			return Erros.TAMANHO_ZERO;

				
		//Corta todos os espaços na expressão infix.
		infix = infix.trim();
				
				
		//Se o tamanho da expressão for 0, devolve erro.
		if(infix.length() == 0)
			return Erros.TAMANHO_ZERO;		
		
		/*c -> Primeiro caracter da expressão infix.
		 *c1 -> Ultimo caracter da expressão infix.*/
		c = infix.charAt(0);
		c1 = infix.charAt(infix.length() - 1);
		
		
		//Se um caracter invalido aparecer no inicio da equação, devolve erro.
		if(c >= 34 && c <= 36 || c >= 38 && c <= 39 || c == 44 || c >= 58 && c <= 64 || c >= 91 && c <= 93 || c >= 95 && c <= 96 || c >= 123 && c <= 126)
			return Erros.CHAR_INVALIDO_INICIO;
		
		
		//Se um caracter aparecer de forma invalida no inicio da expressao, devolve erro.
		if(c == '+' || c == '*' || c == '/' || c == '^' || c == ')' || c == '%' || c == '!')
			return Erros.CHAR_INVALIDO_INICIO;
		
		
		//Se um caracter invalido aparecer no fim da equação, devolve erro.
		if(c1 >= 34 && c1 <= 36 || c1 >= 38 && c1 <= 39 || c1 == 44 || c1 >= 58 && c1 <= 64 || c1 >= 91 && c1 <= 93 || c1 >= 95 && c1 <= 96 || c1 >= 123 && c1 <= 126)
			return Erros.CHAR_INVALIDO_FIM;
		
		
		//Se um caracter aparecer de forma invalida no fim da expressao, devolve erro.
		if(c1 >= 42 &&  c1 <= 47 || c1 == '^' || c1 == '(')
			return Erros.CHAR_INVALIDO_FIM;

		/*Percorre a expressão infix contando os parentesis.*/
		for(int i = 0; i < infix.length(); i++)
		{
			c = infix.charAt(i);
			
			//Conta os parentesis de abrir.
			if(c== '(')
				x++;
			
			//Conta os parentesis de fechar.
			if(c == ')')
				z++;
		}
		
		//Se existirem mais parentesis de abrir, devolve eero.
		if(x > z)
			return Erros.FECHAR_PARENTESIS;
		
		//Se existirem mais parentesis de fechar, devolve erro.
		if(x < z)
			return Erros.ABRIR_PARENTESIS;
		
		
		
		/*Percorre a expressão infix a partir da segunda posição até a penultima.
		 *Vai verificando se existem operadores repetidos, operadores mal posicionados e 
		 *caracteres invalids no meio.*/
		for(int i = 0; i < infix.length() - 2; i++)
		{
			/*c	->	Caracter actual.
			 *c1 ->	Caracter anterior.
			 *c2 -> Caracter seguinte.*/
			c = infix.charAt(i + 1);
			c1 = infix.charAt(i);
			c2 = infix.charAt(i + 2);
			
			//Se o c for um operador e for igual a c1 e c2, devolve erro.
			if(c >= 42 &&  c <= 47 || c == '^')
				if(c == c1 || c == c2)
					return Erros.OPERADORES_REPETIDOS;
			
			//Se o caracter actual for de elevado e c1 ou c2 forem operadores, devolve erro.
			if(c == '^' && ((c2 >= 42 && c2 <= 47) || (c1 >= 42 && c1 <= 47)))
				return Erros.OPERADOR_MAL_POSICIONADO;
			
			//Se o caracter actual for da raiz quadrada e c1 for igual a operadores, devolve erro.
			if(c == '√')
				if(c1 != '+' && c1 != '-' && c1 != '*' && c1 != '/' && c1 != '^')
					return Erros.USO_INVALIDO_RAIZ;
			
			//Se os caracteres actuais forem de factorial ou percentagem e o c2 for um número ou letra, devolve erro.
			if(c == '!' || c == '%')
				if((c2 >= 48 && c2 <= 57) || (c2 >= 65 && c2 <= 90) || (c2 >= 97 && c2 <= 122))
					if(c == '!')
						return Erros.USO_INVALIDO_FACTORIAL;
					else
						return Erros.USO_INVALIDO_PERCENTAGEM;
			
			//Se o actual for um número e c2 e c1 for letra ou pi, então devolve erro.
			if((c >= 48 && c <= 57) && ((c2 >= 65 && c2 <= 90 || c2 >= 97 && c2 <= 122) || (c1 >= 65 && c1 <= 90 || c1 >= 97 && c1 <= 122) || c1 == 'π' | c2 == 'π'))
				return Erros.FALTA_OPERADOR;
			
			//Se o actual for letra e c2 e c1 for número ou pi, então devolve erro.
			if((c >= 65 && c <= 90 || c >= 97 && c <= 122) && ((c2 >= 48 && c2 <= 57) || (c1 >= 48 && c1 <= 57) || c1 == 'π' || c2 == 'π'))
				return Erros.FALTA_OPERADOR;
			
			//Se o actual for pi e c2 e c1 for letra ou número, então devolve erro.
			if(c == 'π' && ((c1 >= 65 && c1 <= 90) || (c1 >= 97 && c1 <= 122) || (c2 >= 65 && c2 <= 90) || (c2 >= 97 && c2 <= 122) || (c2 >= 48 && c2 <= 57) || (c1 >= 48 && c1 <= 57)))
				return Erros.FALTA_OPERADOR;
			
			//Se no meio da expressão encontrar caracteres invalido devolve erro.
			if(c >= 34 && c <= 36 || c >= 38 && c <= 39 || c == 44 || c >= 58 && c <= 64 || c >= 91 && c <= 93 || c >= 95 && c <= 96 || c >= 123 && c <= 126)
				return Erros.CHAR_INVALIDO_MEIO;
		}
		
		return Erros.VALIDO;
	}

	/*Este método cria a arvore binaria a partir da expressão posfix.*/
	public void criarArvore()
	{
		String c;
		String novoNodo = "";
		
		//Se a expressão posfix não estiver construida, este método sai.
		if(posfix.length() == 0)
			return;
		
		pilhaAR = new Pilha<Nodo>(); 
		
		//Percorre a expressão posfix de forma a construir a arvore.
		for(int i = 0; i < posfix.length(); i++)
		{
			//c -> Caracter actual.
			c = String.valueOf(posfix.charAt(i));
			
			//Se c igualar o caracter '|' significa um novo nodo vai ser construido.
			if(c.equals("|"))
			{
				//Se o caracter inicial de novoNodo for diferente de um operando, constroi um novo operando.
				if((novoNodo.charAt(0) < 42 || novoNodo.charAt(0) > 47) && novoNodo.charAt(0) != '^' && novoNodo.charAt(0) != '√')
						criaNovoOperando(novoNodo, i);	//Este método recebe a sub string da expressão posfix para construir um novo nodo da arvore.
				else
					if(novoNodo.charAt(0) == '-' && novoNodo.length() > 1)	//Se o caracter inicial do nodo for um menos e o tamanho da substring for maior que 1. Cria um novo operando negativo.
						criaNovoOperando(novoNodo, i);
					else
						if(novoNodo.charAt(0) == '√') //Se o caracter inicial for de raiz quadrada, constroi um operando com a flag raiz quadrada.
						{
							//Novo nodo raiz quadrada.
							Operador nodo = new Operador(novoNodo, Nodo.RAIZ_QUADRADA);
							
							//O filho deste nodo é igual ao primeiro nodo da pilha que é retirado.
							nodo.setFilho(pilhaAR.pop(), 1);
							
							//Este nodo é introduzido na pilha.
							pilhaAR.push(nodo); 
						}
						else
						{
							//Senão, cria um operador normal.
							Operador nodo = new Operador(novoNodo);
							
							//O filho da direita deste nodo é igual ao primeiro nodo na pilha.
							nodo.setFilho(pilhaAR.pop(), 1);
							
							//O filho da esquerda deste nodo é igual ao seguinte nodo na pilha.
							nodo.setFilho(pilhaAR.pop(), 0);
							
							//Este nodo é posto na pilha.
							pilhaAR.push(nodo);
						}
				
				//Reinicia a substring.
				novoNodo = "";
			}
			else
				novoNodo = novoNodo.concat(c);
			//Concatena em novoNodo o caracter de c.
		}
	}
	
	
	/*Este método serve para cirar um novo operando.*/
	private void criaNovoOperando(String novoNodo, int i)
	{
		//O atributo espc, indica a especialidade do operando.
		int espc = Nodo.NORMAL;
		Variavel var = null;
		
		//Se o ultimo caracter do operando for igual a '!', significa que é factorial.
		if(novoNodo.charAt(novoNodo.length() - 1) == '!')
		{
			espc = Nodo.FACTORIAL;
			
			//Remove o caracter '!' da substring.
			novoNodo = novoNodo.substring(0, novoNodo.length() - 1);
		}
		else
			if(novoNodo.charAt(novoNodo.length() - 1) == '%') //Se o ultimo caracter do operando for igual a '%', significa que é percentagem.
			{
				espc = Nodo.PERCENTAGEM;
				
				//Remove o caracter '%' da substring.
				novoNodo = novoNodo.substring(0, novoNodo.length() - 1);
			}
		
		
		//Se o caracter inicial for pi, então cria um nodo na pilha para o pi.
		if(novoNodo.charAt(0) == 'π')
			pilhaAR.push(new Operando(Math.PI, espc));
		else
		if(novoNodo.charAt(0) >= 48 && novoNodo.charAt(0) <= 57)
			pilhaAR.push(new Operando(Double.parseDouble(novoNodo), espc));	//Se o primeiro caracter for um número, cria um nodo normal na pilha.
		else
		{
			if(novoNodo.charAt(0) == '-')	//Se o primeiro caracter for '-', cria tanto uma várivel negativa como um operando negativo.
			{
				if(novoNodo.charAt(1) >= 48 && novoNodo.charAt(1) <= 57)	//Se o segundo caracter for um número, cria um nodo com número negativo na pilha.
					pilhaAR.push(new Operando(Double.parseDouble(novoNodo), espc));
				else
					var = new Variavel(novoNodo.toLowerCase(), -1, espc, true);	//Senão, é uma várivel e o ultimo parametro é posto como true a indicar a negatividade.
			}
			else
				var = new Variavel(novoNodo.toLowerCase(), 1, espc, false); //Senão, é uma varivel normal.
			
			//Se foi criada uma variavel, introduz essa variavel na lista de variaveis e o nodo na pilha.
			if(var != null)
			{
				pilhaAR.push(var); //Nodo na pilha.
			
				variaveis.add(var); //Variavel na lista.
			}
		}
		
		//Se a o nodo criado for uma percentagem, então actualiza o atributo desse nodo para o ultimo nodo introduzido.
		if(pilhaAR.peek().isEspecial() == Nodo.PERCENTAGEM)
			pilhaAR.peek().setPercVal(lastNum);
		else
			lastNum = pilhaAR.peek(); //Senão, actualiza o ultimo nodo introduzido.
		
	}
	
	
	//Este método serve para resolver a equação a partir da arvore binaria.
	public double resolver()
	{
		//Este nodo vai ser o primeiro do topo da arvore.
		Nodo nodo = pilhaAR.peek();
		
		//Se este nodo for especial, significa que so existe um operando. Resolve-o e devolve.
		if(nodo.isEspecial() == Nodo.PERCENTAGEM || nodo.isEspecial() == Nodo.FACTORIAL || (nodo.isEspecial() == Nodo.RAIZ_QUADRADA && nodo.getFilho(1).getFilho(1) == null))
		{
			//O método resolver especial ,resolve as percentages, factoriais e raizes quadradas.
			resolverEspecial(nodo);
			return nodo.getValor();
		}
		
		//Se o nodo não possuir filhos, devolve o valor deste.
		if(nodo.getFilho(0) == null && nodo.getFilho(1) == null)
			return nodo.getValor();
		
		while(true)
		{
			//Percorre a arvore até atingir a raiz.
			while(true)
			{
				//Se o filho da direita deste nodo possuir pelo menos um filho, prosegue neste direcção.
				if(nodo.getFilho(1).getFilho(1) != null || nodo.getFilho(1).getFilho(0) != null)
					nodo = nodo.getFilho(1);
				else
					if(nodo.isEspecial() != Nodo.RAIZ_QUADRADA) //Se o nodo actual for diferente de raiz quadrada, vai ser o lado esquerdo. O nodo da riaz qudrada so possui filhos no lado direito.
					{
						//Se o filho da esquerda deste nodo possuir pelo menos um filho, prosegue neste direcção.
						if(nodo.getFilho(0).getFilho(1) != null || nodo.getFilho(0).getFilho(0) != null)
							nodo = nodo.getFilho(0);
						else
							break;
					}
					else
						break;
			}
			
			//Para o nodo actual calcula o resultado. Cria um novo Nodo que reflecte o resultado obtido.
			Nodo aux = opera(nodo.getOperador(), nodo.getFilho(1), nodo.getFilho(0));
			
			//Se o nodo actual possuir um pai, o pai deste nodo vai ser pai do novo nodo, substituindo o lugar do actual.
			if(nodo.getPai() != null)
			{
				//Se este nodo for da direita, o novo toma o lugar da direita, senão toma o da esquerda.
				if(nodo.getPai().getFilho(0) == nodo)
					nodo.getPai().setFilho(aux, 0);
				else
					nodo.getPai().setFilho(aux, 1);
			}
			else
				return aux.getValor(); //Caso não possua pai, atingiu o resultado final e devolve.
			
			nodo = nodo.getPai(); //O nodo actual anda para cima.
			
		}
		
	}
	

	//Este método serve para dar um valor a um conjunto de variaveis que partilhem o mesmo nome.
	public void setVar(double val, String var)
	{
		for(int i = 0; i < variaveis.size(); i++)
		{
			if(variaveis.get(i).getVarNome().equals(var))
				variaveis.get(i).setValor(val);	
		}
	}
	
	//Devolve o número total de variaveis.
	public int tamanhoVars()
	{
		return variaveis.size();
	}
	
	//Este método serve para resolver a raiz quadrada, percentagem e factorial.
	private boolean resolverEspecial(Nodo operando)
	{
		double val;
		
		//Se o operando for factorial, resolve o factorial.
		if(operando.isEspecial() == Nodo.FACTORIAL)
		{
			val = 1;
			
			/*Para o número do operando, vai multiplicando sucessivamente pelo número inferior até 1
			 * 4! = 4 * 3 * 2 * 1.*/
			for(int i = 0; i < operando.getValor(); i++)
				val = val * (operando.getValor() - i);	
			
			//Este nodo fica com o novo valor.
			operando.setValor(val);
			
			return true;
		}
		
		
		//Se o operando for percentagem, resolve a percentagem.
		if(operando.isEspecial() == Nodo.PERCENTAGEM)
		{
			//Se este operando não possuir um pai nem valor de percentagem, devolve 0.
			if(operando.getPai() == null || operando.getPercVal() == null)
				val = 0;
			else
				val = operando.getPercVal().getValor() * (operando.getValor()/100);	//Senão, faz a percentagem: (valor ao qual a percentagem incide) / (percentagem/100)
			
			operando.setValor(val);
			
			return true;
		}
		
		//Se o operador for a raiz quadrada, então resolve-a.
		if(operando.isEspecial() == Nodo.RAIZ_QUADRADA)
		{
			//O valor vai ser igual ao á função da raiz quadrada do valor do filho da direita deste nodo.
			val = Math.sqrt(operando.getFilho(1).getValor());
			
			operando.setValor(val);
			
			return true;
		}
		
		return false;
		
	}
	
	/*Executa as operações de forma a resolver a equação.
	 * Os parametros são o operador,
	 * operando1 que é o filho da direita e o 
	 * operando2 que é o filho da esquerda.*/
	private Nodo opera(String operador, Nodo operando1, Nodo operando2)
	{
		Nodo nodo = null;
		
		//Se o operando 1 for especial, utiliza o resolverEspecial.
		if(operando1.isEspecial() != Nodo.NORMAL)
			resolverEspecial(operando1);
		
		//Se o operando2 for diferente de nulo, verifica se é especial e se sim resolve-o.
		if(operando2 != null)
			if(operando2.isEspecial() != Nodo.NORMAL)
				resolverEspecial(operando2);
		
		
		//Para cada tipo diferente de operador, resolve a operação criando um novo nodo.
		switch(operador)
		{
			case "+":
				
				nodo = new Operando(operando2.getValor() + operando1.getValor());
				
				break;
				
			case "-":
				
				nodo = new Operando(operando2.getValor() - operando1.getValor());
				
				break;
				
			case "/":
				
				nodo = new Operando(operando2.getValor() / operando1.getValor());
				
				break;
				
			case "*":
				
				nodo = new Operando(operando2.getValor() * operando1.getValor());
			
				break;
				
			case "^":
				
				nodo = new Operando(Math.pow(operando2.getValor(), operando1.getValor()));
				
				break;
				
			case "√":
				//Neste caso é se a operação dentro da riaz possuir mais do que um número.
				nodo = new Operando(Math.sqrt(operando1.getValor()));
				
				break;
				
			default:
					
		}
		
		//Devolve o novo nodo.
		return nodo;
	}
	
	
	//Escreve na expressão posfix a string numero.
	private String escreverNum(String numero)
	{
		if(numero.length() > 0)
		{
			posfix = posfix.concat(numero + "|");
		}
		
		return "";
	}
	
	
	//Função prioridade baixa, serve para introduzir na pilha os operadores + e - e escrever outros na expressão posfix.
	private void prioridadeBaixa(String c)
	{
		//Se a pilha não estiver vazia, vai remover para o posfix todos os operadores na pilha até atingir um operador igual ao do parametro ou o caracter "(".
		if(pilhaST.tamanho() > 0) 
		{
			//Enquanto diferente do parametro e de "(", escreve os operadores da pilha pra o posfix.
			while(!pilhaST.peek().equals(c) && !pilhaST.peek().equals("("))
			{
				posfix = posfix.concat(pilhaST.pop() + "|");
				
				if(pilhaST.tamanho() == 0) //Se a pilha estiver vazia, sai.
					break;
			}
			
			pilhaST.push(c); //Escreve este operando na pilha.
		}
		else
			pilhaST.push(c);
	}
	
	//Função prioridade média, serve para introduzir na pilha os operadores * e / e escrever outros na expressão posfix.
	private void prioridadeMedia(String c)
	{
		//Se a pilha não estiver vazia, vai remover todos os operadores da pilha de maior ou igual prioridade.
		if(pilhaST.tamanho() > 0)
		{
			while(pilhaST.peek().equals("^") || pilhaST.peek().equals("*") || pilhaST.peek().equals("/"))
			{
				posfix = posfix.concat(pilhaST.pop() + "|");
				
				if(pilhaST.tamanho() == 0)
					break;
			}
			
			pilhaST.push(c);
		}
		else
			pilhaST.push(c);
	}
	
	//Função prioridade alta, serve para introduzir na pilha os operadores ^, (, ) e √ e escrever outros na expressão posfix.
	private void prioridadeAlta(String c)
	{
		pilhaST.push(c);
	}
	
	
	//Este método executa quando é encontrado um fechar parentesis. Remove todos os operadores da pilha até encontrar um abrir parentesis.
	private void fimParentesis()
	{
		while(!pilhaST.peek().equals("("))
		{
			posfix = posfix.concat(pilhaST.pop() + "|");
		}
		
		pilhaST.pop();
	}

	//Devolve a expressão infix.
	public String getInfix() 
	{
		return infix;
	}

	//Devolve a expressão posfix.
	public String getPosfix() 
	{
		return posfix;
	}

	public String getNome() 
	{
		return nome;
	}

	public void setNome(String nome)
	{
		this.nome = nome;
	}

	public ArrayList<String> getVariaveis()
	{
		ArrayList<String> varNames = new ArrayList<String>();
		
		for(int i = 0; i < variaveis.size(); i++)
			if(!varNames.contains(variaveis.get(i).getVarNome()))
				varNames.add(variaveis.get(i).getVarNome());
		
		return varNames;
	}
	
}
